﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;
using MessageServer.utilities;
using MessageServer.logging;

namespace MessageServer.pool
{
	public abstract class IPoolableObject : IDisposable
	{
        internal StrictObjectPool _belongTo;
        internal PoolObject _iam;

        protected IPoolableObject()
        {
        }

        internal void identity(StrictObjectPool pool, PoolObject obj)
        {
            _belongTo = pool;
            _iam = obj;
        }

        public void releaseme()
        {
            _belongTo.release(_iam);
        }

        public void discardme()
        {
            _belongTo.discard(_iam);
        }

        public virtual void Dispose()
        {
        }
	}

    public interface IPoolableObjectCreator
    {
        IPoolableObject create();
    }

    public class DefaultCreator<T> : IPoolableObjectCreator
    {
        //invokes the default constructor with no parameters
        public IPoolableObject create()
        {
            IPoolableObject newobj;

            Type t = typeof(T);
            ConstructorInfo constructor = t.GetConstructor(new Type[0]);
            newobj = (IPoolableObject)constructor.Invoke(new Object[0]);
            if (!(newobj is IPoolableObject))
                throw new ReflectionTypeLoadException(new Type[] { typeof(T) }, null, "Cannot instantiate Non-Poolable object");

            return (IPoolableObject)newobj;
        }
    }

    public class PoolObject
    {
        private bool _inUse;
        private IPoolableObject _object;
        private bool _valid;

        public PoolObject(IPoolableObject obj)
        {
            _object = obj;
            _inUse = false;
            _valid = true;
        }

        internal bool dummy { get { return (_object == null); } }
        internal IPoolableObject poolobject { get { return _object;  } }
        internal bool inuse { get { return _inUse; } set { _inUse = value; } }
        internal bool valid { get { return _valid; } set { _valid = value; } }
    }

    /**
     * In this case the number of objects does not go beyond the max specified. The pool
     * waits till an object is available or times out.
     * */
    public class StrictObjectPool : IDisposable
    {
        protected const int DEFAULT_OBJECTS = 10;
        protected const int DEFAULT_WAIT = 100; //100 ms total wait, will check every 5ms for availability(??)

        private int _minobjects;                    //min objects that will always be present in pool
        private int _maxobjects;                    //max objects that will be created in the pool.
        private int _waitTime;                      //the time to wait for a free object
        private IPoolableObjectCreator _creator; //a creator for the object to be pooled

        protected TSList<PoolObject> _pool;           //all objects are present here. They can be free or in use.
        private TSList<PoolObject> _freePool;       //only free objects are present here.
        private int _currentpoolcnt;                //total number of objects in pool
        private readonly object _objectAvailable = new object(); //used to flag availability

        public StrictObjectPool(IPoolableObjectCreator creator)
        {
            init(creator, DEFAULT_WAIT, DEFAULT_OBJECTS, DEFAULT_OBJECTS);
        }

        public StrictObjectPool(IPoolableObjectCreator creator, int waittime,
            int minobjects, int maxobjects)
        {
            init(creator, waittime, minobjects, maxobjects);
        }
        
        /**
         * Constructor. The StrictObjectPool is created using the creator for the object to be
         * pooled. The waittime, minobjects and maxobjects are defaulted and can be changed.
         */
        public void init(IPoolableObjectCreator creator, int waittime, 
            int minobjects, int maxobjects)
        {
            _minobjects = minobjects;
            _maxobjects = maxobjects;
            _waitTime = waittime;
            PoolObject obj = new PoolObject(null);
            _pool = new TSList<PoolObject>(obj);
            _freePool = new TSList<PoolObject>(obj);
            _creator = creator;
            if (_creator == null)
                throw new Exception("Cannot pool without constructor");
            extendPool(_minobjects); //extend the pool for the minimum objects
        }

        private void extendPool()
        {
            extendPool(2);
        }

        private void extendPool(int min)
        {
            if (_currentpoolcnt < _maxobjects)
            {
                int increment = min;
                if (_currentpoolcnt + min > _maxobjects) increment = (_maxobjects - _currentpoolcnt);
                for (int i = 0; i < increment; i++)
                {
                    //need to find how to create it here.
                    int initialval = _currentpoolcnt;
                    int val = Interlocked.Increment(ref _currentpoolcnt); //first try to get a incremented value
                    if (val >= _maxobjects)
                        break; //reached cnt

                    IPoolableObject myobj = _creator.create();
                    PoolObject obj = new PoolObject(myobj);
                    myobj.identity(this, obj);
                    _pool.push(obj);
                    objectadded(obj);
                    _freePool.push(obj);
                }
            }
        }

        protected virtual void objectadded(PoolObject o)
        {
            //can be overridden. By default does nothing
        }

        /**
         * The object is released back into the pool. Currently no check is done to see if this is
         * actually from the pool. Will be done(TODO:)
         * */
        public virtual void release(PoolObject obj)
        {
            if ((!obj.inuse) || (obj.dummy))
                throw new Exception("Object not in use. Cannot free.");

            obj.inuse = false;
            _freePool.push(obj);

            lock (_objectAvailable)
            {
                Monitor.Pulse(_objectAvailable);
            }
        }

        public void discard(PoolObject obj)
        {
            //mark the object as invalid, so that the next get pops it out of the free pool.
            //currently invalid objects will also contribute to the pool size. Maybe this has to change.
            //TODO:
            obj.valid = false;
        }

        /*
         * Call to get the next object that can be used. This is thread safe and can be called
         * in multiple threads. Will wait till an object is available for waittime if no objects
         * are currently available. If the number of pooled objects is less than max objects then
         * a new object is created and returned immediately.
         * */
        public virtual PoolObject get()
        {
            PerfStat s = new PerfStat();
            s.start("StrictObjectPool:get");
            PoolObject nextobj = null;

            nextobj = _freePool.pop();
            while ((!nextobj.dummy) && (!nextobj.valid))
                nextobj = _freePool.pop(); //pop till the returned value is invalid.

            if (nextobj.dummy)
            {
                extendPool(); //try extending then get the object
                nextobj = _freePool.pop();
            }

            if (nextobj.dummy) //if object is still null then means we have exceed max.
            {
                bool gotobject = false;
                lock (_objectAvailable)
                {
                    //wait for someone to free for a given waittime
                    gotobject = Monitor.Wait(_objectAvailable, _waitTime);
                }

                if (gotobject) //did not timeout.
                {
                    //if someone signalled, then object is available.
                    //somehow this is having a problem let me check the other thing
                    //nextobj = get();
                    nextobj = _freePool.pop();
                }
            }

            s.dumpperf(UtilityLogger.LOG);
            if ((nextobj == null) || nextobj.dummy)
                return null; //return null if it is the dummy object still
            else
            {
                nextobj.inuse = true;
                return nextobj;
            }
        }

        public void Dispose()
        {
            PoolObject obj = _pool.pop();
            while (!obj.dummy)
            {
                obj.poolobject.Dispose();
                obj = _pool.pop();
            }
        }
    }
}
